import debug from "debug";
import fsp from "node:fs/promises";
import path from "node:path";
import type { Plugin, ResolvedConfig } from "vite";
import { CLIENT_MANIFEST_RELATIVE_PATH } from "../lib/constants.mjs";
import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";

const log = debug("rwsdk:vite:linker-plugin");

export function linkWorkerBundle({
  code,
  manifestContent,
  projectRootDir,
  base,
}: {
  code: string;
  manifestContent: string;
  projectRootDir: string;
  base?: string;
}) {
  let newCode = code;
  const manifest = JSON.parse(manifestContent);

  // 1. Replace the manifest placeholder with the actual manifest content.
  log("Injecting manifest into worker bundle");
  newCode = newCode.replace(
    /['"]__RWSDK_MANIFEST_PLACEHOLDER__['"]/,
    manifestContent,
  );

  // 2. Replace asset placeholders with their final hashed paths.
  log("Replacing asset placeholders in final worker bundle");
  for (const [key, value] of Object.entries(manifest)) {
    const normalizedKey = normalizeModulePath(key, projectRootDir, {
      isViteStyle: false,
    });

    // If base is provided, prepend it with the final hashed path.
    // Base is assumed to have a trailing "/".
    const assetPath = (base ? base : "/") + (value as { file: string }).file;

    newCode = newCode.replaceAll(`rwsdk_asset:${normalizedKey}`, assetPath);
  }

  // 3. Deprefix any remaining placeholders that were not in the manifest.
  // This handles public assets that don't go through the bundler.
  log("Deprefixing remaining asset placeholders");
  newCode = newCode.replaceAll("rwsdk_asset:", "");

  return {
    code: newCode,
    map: null,
  };
}

export const linkerPlugin = ({
  projectRootDir,
}: {
  projectRootDir: string;
}): Plugin => {
  let config: ResolvedConfig;

  return {
    name: "rwsdk:linker",

    configResolved(resolvedConfig) {
      config = resolvedConfig;
    },

    async renderChunk(code) {
      if (
        this.environment.name !== "worker" ||
        process.env.RWSDK_BUILD_PASS !== "linker"
      ) {
        return null;
      }

      log("Rendering final worker chunk");
      const manifestContent = await fsp.readFile(
        path.resolve(projectRootDir, CLIENT_MANIFEST_RELATIVE_PATH),
        "utf-8",
      );

      const result = linkWorkerBundle({
        code,
        manifestContent,
        projectRootDir,
        base: config.base,
      });

      log("Final worker chunk rendered");
      return result;
    },
  };
};
